require "../../spec_helper"

describe "Semantic: lib" do
  it "types a varargs external" do
    assert_type("lib LibFoo; fun bar(x : Int32, ...) : Int32; end; LibFoo.bar(1, 1.5, 'a')") { int32 }
  end

  it "raises on undefined fun" do
    assert_error <<-CRYSTAL, "undefined fun 'foo' for LibC"
      lib LibC
      end

      LibC.foo
      CRYSTAL
  end

  it "raises wrong number of arguments" do
    assert_error <<-CRYSTAL, "wrong number of arguments for 'LibC#foo' (given 1, expected 0)"
      lib LibC
        fun foo : Int32
      end

      LibC.foo 1
      CRYSTAL
  end

  it "raises wrong argument type" do
    assert_error <<-CRYSTAL, "argument 'x' of 'LibC#foo' must be Int32, not Char"
      lib LibC
        fun foo(x : Int32) : Int32
      end

      LibC.foo 'a'
      CRYSTAL
  end

  it "reports error when changing var type and something breaks" do
    assert_error <<-CRYSTAL, "undefined method '+' for Char"
      class LibFoo
        def initialize
          @value = 1
        end

        def value
          @value
        end

        def value=(@value : Char)
        end
      end

      f = LibFoo.new
      f.value + 1
      f.value = 'a'
      CRYSTAL
  end

  it "reports error when changing instance var type and something breaks" do
    assert_error <<-CRYSTAL, "argument 'c' of 'Lib#bar' must be Char"
      lib Lib
        fun bar(c : Char)
      end

      class Foo
        def value=(@value : Int32 | Char)
        end

        def value
          @value
        end
      end

      def foo(x)
        x.value = 'a'
        Lib.bar x.value
      end

      f = Foo.new
      foo(f)

      f.value = 1
      CRYSTAL
  end

  it "reports error on fun argument type not primitive like" do
    assert_error "lib LibFoo; fun foo(x : Reference); end",
      "only primitive types"
  end

  it "reports error on fun argument type not primitive like, Nil (#2994)" do
    assert_error "lib LibFoo; fun foo(x : Nil); end",
      "only primitive types"
  end

  it "reports error on fun return type not primitive like" do
    assert_error "lib LibFoo; fun foo : Reference; end",
      "only primitive types"
  end

  it "reports error on struct field type not primitive like" do
    assert_error "lib LibFoo; struct Foo; x : Reference; end; end",
      "only primitive types"
  end

  it "reports error on typedef type not primitive like" do
    assert_error "lib LibFoo; type Foo = Reference; end",
      "only primitive types"
  end

  it "reports error out can only be used with lib funs" do
    assert_error "foo(out x)",
      "out can only be used with lib funs"
  end

  it "reports error out can only be used with lib funs in named argument" do
    assert_error "foo(x: out x)",
      "out can only be used with lib funs"
  end

  it "reports error if using out with an already declared variable" do
    assert_error <<-CRYSTAL, "variable 'x' is already defined, `out` must be used to define a variable, use another name", inject_primitives: true
      lib Lib
        fun foo(x : Int32*)
      end

      x = Pointer(Int32).malloc(1_u64)
      Lib.foo out x
      CRYSTAL
  end

  it "allows invoking out with underscore " do
    assert_type(<<-CRYSTAL) { float64 }
      lib Lib
        fun foo(x : Int32*) : Float64
      end

      Lib.foo out _
      CRYSTAL
  end

  it "reports redefinition of fun with different signature" do
    assert_error <<-CRYSTAL, "fun redefinition with different signature"
      lib LibC
        fun foo : Int32
        fun foo : Int64
      end
      CRYSTAL
  end

  it "types lib var get" do
    assert_type(<<-CRYSTAL) { int32 }
      lib LibC
        $errno : Int32
      end

      LibC.errno
      CRYSTAL
  end

  it "types lib var set" do
    assert_type(<<-CRYSTAL) { int32 }
      lib LibC
        $errno : Int32
      end

      LibC.errno = 1
      CRYSTAL
  end

  it "types lib var get with forward declaration" do
    assert_type(<<-CRYSTAL) { int32 }
      lib LibC
        $errno : A

        alias A = Int32
      end

      LibC.errno
      CRYSTAL
  end

  it "defined fun with aliased type" do
    assert_type(<<-CRYSTAL) { int32 }
      lib LibC
        alias SizeT = Int32
        fun foo(x : SizeT) : SizeT
      end

      LibC.foo(1)
      CRYSTAL
  end

  it "overrides definition of fun" do
    result = assert_type(<<-CRYSTAL) { float64 }
      lib LibC
        fun foo(x : Int32) : Float64
      end

      lib LibC
        fun foo = bar(x : Int32) : Float64
      end

      LibC.foo(1)
      CRYSTAL
    mod = result.program
    lib_type = mod.types["LibC"].as(LibType)
    foo = lib_type.lookup_first_def("foo", false).as(External)
    foo.real_name.should eq("bar")
  end

  it "error if passing type to LibC with to_unsafe but type doesn't match" do
    assert_error <<-CRYSTAL, "argument 'x' of 'LibC#foo' must be Int32, not Foo (nor Char returned by 'Foo#to_unsafe')"
      lib LibC
        fun foo(x : Int32) : Int32
      end

      class Foo
        def to_unsafe
          'a'
        end
      end

      LibC.foo Foo.new
      CRYSTAL
  end

  it "error if passing non primitive type as varargs" do
    assert_error <<-CRYSTAL, "argument #2 of 'LibC#foo' is not a primitive type and no Foo#to_unsafe method found"
      lib LibC
        fun foo(x : Int32, ...)
      end

      class Foo
      end

      LibC.foo 1, Foo.new
      CRYSTAL
  end

  it "error if passing non primitive type as varargs invoking to_unsafe" do
    assert_error <<-CRYSTAL, "converted Foo invoking to_unsafe, but Bar is not a primitive type"
      lib LibC
        fun foo(x : Int32, ...)
      end

      class Bar
      end

      class Foo
        def to_unsafe
          Bar.new
        end
      end

      LibC.foo 1, Foo.new
      CRYSTAL
  end

  it "allows passing splat to LibC fun" do
    assert_type(<<-CRYSTAL) { float64 }
      lib LibC
        fun foo(x : Int32, y : Float64, ...) : Float64
      end

      t = {1, 2.5, 3, 4}
      LibC.foo *t
      CRYSTAL
  end

  it "allows passing double splat to LibC fun" do
    assert_type(<<-CRYSTAL, inject_primitives: true) { float64 }
      lib LibC
        fun foo(x : Int32, y : Float64) : Float64
      end

      t = {y: 2.5, x: 3}
      LibC.foo **t
      CRYSTAL
  end

  it "errors if missing link arguments" do
    assert_error <<-CRYSTAL, "missing link arguments: must at least specify a library name"
      @[Link]
      lib LibFoo
      end
      CRYSTAL
  end

  it "errors if first argument is not a string" do
    assert_error <<-CRYSTAL, "'lib' link argument must be a String"
      @[Link(1)]
      lib LibFoo
      end
      CRYSTAL
  end

  it "errors if second argument is not a string" do
    assert_error <<-CRYSTAL, "'ldflags' link argument must be a String"
      @[Link("foo", 1)]
      lib LibFoo
      end
      CRYSTAL
  end

  it "errors if third argument is not a bool" do
    assert_error <<-CRYSTAL, "'static' link argument must be a Bool"
      @[Link("foo", "bar", 1)]
      lib LibFoo
      end
      CRYSTAL
  end

  it "errors if fourth argument is not a bool" do
    assert_error <<-CRYSTAL, "'framework' link argument must be a String"
      @[Link("foo", "bar", true, 1)]
      lib LibFoo
      end
      CRYSTAL
  end

  it "errors if too many link arguments" do
    assert_error <<-CRYSTAL, "wrong number of link arguments (given 5, expected 1..4)"
      @[Link("foo", "bar", true, "Cocoa", 1)]
      lib LibFoo
      end
      CRYSTAL
  end

  it "errors if unknown named arg" do
    assert_error <<-CRYSTAL, "unknown link argument: 'boo' (valid arguments are 'lib', 'ldflags', 'static', 'pkg_config', 'framework', 'wasm_import_module', and 'dll')"
      @[Link(boo: "bar")]
      lib LibFoo
      end
      CRYSTAL
  end

  it "allows dll argument" do
    assert_no_errors <<-CRYSTAL
      @[Link(dll: "foo.dll")]
      lib LibFoo
      end
      CRYSTAL

    assert_no_errors <<-CRYSTAL
      @[Link(dll: "BAR.DLL")]
      lib LibFoo
      end
      CRYSTAL
  end

  it "errors if dll argument contains directory separators" do
    assert_error <<-CRYSTAL, "'dll' link argument must not include directory separators"
      @[Link(dll: "foo/bar.dll")]
      lib LibFoo
      end
      CRYSTAL

    assert_error <<-CRYSTAL, "'dll' link argument must not include directory separators"
      @[Link(dll: %q(foo\\bar.dll))]
      lib LibFoo
      end
      CRYSTAL
  end

  it "errors if dll argument does not end with '.dll'" do
    assert_error <<-CRYSTAL, "'dll' link argument must use a '.dll' file extension"
      @[Link(dll: "foo")]
      lib LibFoo
      end
      CRYSTAL

    assert_error <<-CRYSTAL, "'dll' link argument must use a '.dll' file extension"
      @[Link(dll: "foo.dylib")]
      lib LibFoo
      end
      CRYSTAL

    assert_error <<-CRYSTAL, "'dll' link argument must use a '.dll' file extension"
      @[Link(dll: "")]
      lib LibFoo
      end
      CRYSTAL
  end

  it "errors if lib already specified with positional argument" do
    assert_error <<-CRYSTAL, "'lib' link argument already specified"
      @[Link("foo", lib: "bar")]
      lib LibFoo
      end
      CRYSTAL
  end

  it "errors if lib named arg is not a String" do
    assert_error <<-CRYSTAL, "'lib' link argument must be a String"
      @[Link(lib: 1)]
      lib LibFoo
      end
      CRYSTAL
  end

  it "clears annotations after lib" do
    assert_type(<<-CRYSTAL) { int32 }
      @[Link("foo")]
      lib LibFoo
        fun foo
      end
      1
      CRYSTAL
  end

  it "warns if @[Link(static: true)] is specified" do
    assert_warning <<-CRYSTAL,
      @[Link("foo", static: true)]
      lib Foo
      end
      CRYSTAL
      "warning in line 1\nWarning: specifying static linking for individual libraries is deprecated"
  end

  it "warns if Link annotations use positional arguments" do
    assert_warning <<-CRYSTAL,
      @[Link("foo", "bar")]
      lib Foo
      end
      CRYSTAL
      "warning in line 1\nWarning: using non-named arguments for Link annotations is deprecated"
  end

  it "allows invoking lib call without obj inside lib" do
    assert_type(<<-CRYSTAL) { int32 }
      lib LibFoo
        fun foo : Int32

        A = foo
      end

      LibFoo::A
      CRYSTAL
  end

  it "errors if lib fun call is part of dispatch" do
    assert_error <<-CRYSTAL, "lib fun call is not supported in dispatch"
      lib LibFoo
        fun foo : Int32
      end

      class Bar
        def self.foo
        end
      end

      (LibFoo || Bar).foo
      CRYSTAL
  end

  it "disallows passing nil or pointer to arg expecting pointer" do
    assert_error <<-CRYSTAL, "argument 'x' of 'Foo#foo' must be Pointer(Int32), not (Pointer(Int32) | Nil)", inject_primitives: true
      lib Foo
        fun foo(x : Int32*) : Int64
      end

      a = 1 == 1 ? nil : Pointer(Int32).malloc(1_u64)
      Foo.foo(a)
      CRYSTAL
  end

  it "correctly attached link flags if there's a macro if" do
    result = semantic(<<-CRYSTAL)
      @[Link("SDL")]
      @[Link("SDLMain")]
      {% if flag?(:some_flag) %}
        @[Link(framework: "Cocoa")]
      {% end %}
      lib LibSDL
        fun init = SDL_Init(flags : UInt32) : Int32
      end

      LibSDL.init(0_u32)
      CRYSTAL
    sdl = result.program.types["LibSDL"].as(LibType)
    attrs = sdl.link_annotations.not_nil!
    attrs.size.should eq(2)
    attrs[0].lib.should eq("SDL")
    attrs[1].lib.should eq("SDLMain")
  end

  it "supports forward references (#399)" do
    assert_type(<<-CRYSTAL) { pointer_of(types["LibFoo"].types["Bar"]) }
      lib LibFoo
        fun foo() : Bar*

        struct Bar
          x : Int32
        end
      end

      LibFoo.foo
      CRYSTAL
  end

  it "supports forward references with struct inside struct (#399)" do
    assert_type(<<-CRYSTAL) { pointer_of(types["LibFoo"].types["Foo"]) }
      lib LibFoo
        struct Bar
          x : Foo*
        end

        struct Foo
          x : Int32
        end
      end

      LibFoo::Bar.new.x
      CRYSTAL
  end

  it "errors if defines def on lib" do
    assert_error <<-CRYSTAL, "can't define method in lib LibC"
      lib LibC
      end

      def LibC.foo
      end
      CRYSTAL
  end

  it "reopens lib and adds more link annotations" do
    result = semantic(<<-CRYSTAL)
      @[Link("SDL")]
      lib LibSDL
        fun init = SDL_Init(flags : UInt32) : Int32
      end

      @[Link("SDLMain")]
      lib LibSDL
      end

      LibSDL.init(0_u32)
      CRYSTAL
    sdl = result.program.types["LibSDL"].as(LibType)
    attrs = sdl.link_annotations.not_nil!
    attrs.size.should eq(2)
    attrs[0].lib.should eq("SDL")
    attrs[1].lib.should eq("SDLMain")
  end

  it "reopens lib and adds same link annotations" do
    result = semantic(<<-CRYSTAL)
      @[Link("SDL")]
      lib LibSDL
        fun init = SDL_Init(flags : UInt32) : Int32
      end

      @[Link("SDL")]
      lib LibSDL
      end

      LibSDL.init(0_u32)
      CRYSTAL
    sdl = result.program.types["LibSDL"].as(LibType)
    attrs = sdl.link_annotations.not_nil!
    attrs.size.should eq(1)
    attrs[0].lib.should eq("SDL")
  end

  it "gathers link annotations from macro expression" do
    result = semantic(<<-CRYSTAL)
      {% begin %}
        @[Link("SDL")]
      {% end %}
      lib LibSDL
        fun init = SDL_Init : Int32
      end

      LibSDL.init
      CRYSTAL
    sdl = result.program.types["LibSDL"].as(LibType)
    attrs = sdl.link_annotations.not_nil!
    attrs.size.should eq(1)
    attrs[0].lib.should eq("SDL")
  end

  it "errors if using void as parameter (related to #508)" do
    assert_error <<-CRYSTAL, "can't use Void as parameter type"
      lib LibFoo
        fun foo(x : Void)
      end
      CRYSTAL
  end

  it "errors if using void via typedef as parameter (related to #508)" do
    assert_error <<-CRYSTAL, "can't use Void as parameter type"
      lib LibFoo
        type Foo = Void
        fun foo(x : Foo)
      end
      CRYSTAL
  end

  it "can use tuple as fun return" do
    assert_type(<<-CRYSTAL) { tuple_of([int32, int32] of TypeVar) }
      lib LibC
        fun foo : {Int32, Int32}
      end

      LibC.foo
      CRYSTAL
  end

  it "doesn't try to invoke unsafe for c struct/union (#1362)" do
    assert_error <<-CRYSTAL, "argument 'x' of 'LibFoo#foo' must be Pointer(LibFoo::Bar), not LibFoo::Bar"
      lib LibFoo
        struct Bar
        end

        fun foo(x : Bar*)
      end

      bar = LibFoo::Bar.new
      LibFoo.foo(bar)
      CRYSTAL
  end

  it "passes int as another integer type in variable" do
    assert_type(<<-CRYSTAL, inject_primitives: true) { float64 }
      lib LibFoo
        fun foo(x : Int32) : Float64
      end

      a = 1_u8
      LibFoo.foo a
      CRYSTAL
  end

  it "passes float as another integer type in variable" do
    assert_type(<<-CRYSTAL, inject_primitives: true) { int32 }
      lib LibFoo
        fun foo(x : Float32) : Int32
      end

      a = 1_f64
      LibFoo.foo a
      CRYSTAL
  end

  it "passes int as another integer type with literal" do
    assert_type(<<-CRYSTAL) { float64 }
      lib LibFoo
        fun foo(x : Int32) : Float64
      end

      LibFoo.foo 1_u8
      CRYSTAL
  end

  it "errors if invoking to_i32! and got error in that call" do
    assert_error <<-CRYSTAL, "converting from Foo to Int32 by invoking 'to_i32!'"
      lib LibFoo
        fun foo(x : Int32) : Float64
      end

      class Foo
        def to_i32!
          1 + 'a'
        end
      end

      LibFoo.foo Foo.new
      CRYSTAL
  end

  it "errors if invoking to_i32! and got wrong type" do
    assert_error <<-CRYSTAL, "invoked 'to_i32!' to convert from Foo to Int32, but got Char"
      lib LibFoo
        fun foo(x : Int32) : Float64
      end

      class Foo
        def to_i32!
          'a'
        end
      end

      LibFoo.foo Foo.new
      CRYSTAL
  end

  it "defines lib funs before funs with body" do
    assert_type(<<-CRYSTAL) { int32 }
      fun foo : Int32
        LibX.x
      end

      lib LibX
        fun x : Int32
      end

      foo
      CRYSTAL
  end

  it "errors if using out with varargs" do
    assert_error <<-CRYSTAL, "can't use out at varargs position: declare the variable with `z = uninitialized ...` and pass it with `pointerof(z)`"
      lib LibX
        fun x(...)
      end

      LibX.x(out z)
      CRYSTAL
  end

  it "errors if using out with void pointer (#2424)" do
    assert_error <<-CRYSTAL, "can't use out with Void* (parameter 'x' of LibFoo.foo is Void*)"
      lib LibFoo
        fun foo(x : Void*)
      end

      LibFoo.foo(out x)
      CRYSTAL
  end

  it "errors if using out with void pointer through type" do
    assert_error <<-CRYSTAL, "can't use out with Void* (parameter 'x' of LibFoo.foo is Void*)"
      lib LibFoo
        type Foo = Void
        fun foo(x : Foo*)
      end

      LibFoo.foo(out x)
      CRYSTAL
  end

  it "errors if using out with non-pointer" do
    assert_error <<-CRYSTAL, "parameter 'x' of LibFoo.foo cannot be passed as 'out' because it is not a pointer"
      lib LibFoo
        fun foo(x : Int32)
      end

      LibFoo.foo(out x)
      CRYSTAL
  end

  it "errors if redefining fun with different signature (#2468)" do
    assert_error <<-CRYSTAL, "fun redefinition with different signature"
      fun foo
      end

      fun foo(x : Int32)
      end
      CRYSTAL
  end

  it "errors if using named args with variadic function" do
    assert_error <<-CRYSTAL, "can't use named args with variadic function"
      lib LibC
        fun foo(x : Int32, y : UInt8, ...) : Int32
      end

      LibC.foo y: 1_u8, x: 1
      CRYSTAL
  end

  it "errors if using unknown named param" do
    assert_error <<-CRYSTAL, "no parameter named 'z'"
      lib LibC
        fun foo(x : Int32, y : UInt8) : Int32
      end

      LibC.foo y: 1_u8, x: 1, z: 2
      CRYSTAL
  end

  it "errors if parameter already specified" do
    assert_error <<-CRYSTAL, "argument for parameter 'x' already specified"
      lib LibC
        fun foo(x : Int32, y : UInt8) : Int32
      end

      LibC.foo 1, x: 2
      CRYSTAL
  end

  it "errors if missing argument" do
    assert_error <<-CRYSTAL, "missing argument: y"
      lib LibC
        fun foo(x : Int32, y : UInt8) : Int32
      end

      LibC.foo x: 2
      CRYSTAL
  end

  it "errors if missing arguments" do
    assert_error <<-CRYSTAL, "missing arguments: x, z"
      lib LibC
        fun foo(x : Int32, y : UInt8, z: Int32) : Int32
      end

      LibC.foo y: 1_u8
      CRYSTAL
  end

  it "can use named args" do
    assert_type(<<-CRYSTAL) { int32 }
      lib LibC
        fun foo(x : Int32, y : UInt8) : Int32
      end

      LibC.foo y: 1_u8, x: 1
      CRYSTAL
  end

  it "can use out with named args" do
    assert_type(<<-CRYSTAL) { int32 }
      lib LibC
        fun foo(x : Int32*)
      end

      LibC.foo(x: out x)
      x
      CRYSTAL
  end

  it "types fun returning nothing as nil" do
    assert_type(<<-CRYSTAL) { nil_type }
      lib LibFoo
        fun foo
      end

      LibFoo.foo
      CRYSTAL
  end

  it "types fun returning void as nil" do
    assert_type(<<-CRYSTAL) { nil_type }
      lib LibFoo
        fun foo : Void
      end

      LibFoo.foo
      CRYSTAL
  end

  it "types fun returning nil as nil" do
    assert_type(<<-CRYSTAL) { nil_type }
      lib LibFoo
        fun foo : Nil
      end

      LibFoo.foo
      CRYSTAL
  end

  it "can use macros inside lib" do
    assert_type(<<-CRYSTAL) { int32 }
      lib LibFoo
        {% begin %}
          fun foo : Int32
        {% end %}
      end

      LibFoo.foo
      CRYSTAL
  end

  it "can use macros inside struct" do
    assert_type(<<-CRYSTAL) { int32 }
      lib LibFoo
        struct Foo
          {% begin %}
            x : Int32
          {% end %}
        end
      end

      LibFoo::Foo.new.x
      CRYSTAL
  end

  it "errors if defining incompatible funs with the same name in the same lib (#3045)" do
    assert_error <<-CRYSTAL, "fun redefinition with different signature"
      lib LibFoo
        fun foo1 = foo
        fun foo2 = foo(x : Int32)
      end
      CRYSTAL
  end

  it "errors if defining incompatible funs with the same name in different libs (#3045)" do
    assert_error <<-CRYSTAL, "fun redefinition with different signature"
      lib LibFoo1
        fun foo1 = foo
      end

      lib LibFoo2
        fun foo2 = foo(x : Int32)
      end
      CRYSTAL
  end

  it "specifies a call convention" do
    result = semantic(<<-CRYSTAL)
      lib LibFoo
        @[CallConvention("X86_StdCall")]
        fun foo : Int32
      end
      CRYSTAL
    foo = result.program.types["LibFoo"].lookup_first_def("foo", nil).as(External)
    foo.call_convention.should eq(LLVM::CallConvention::X86_StdCall)
  end

  it "specifies a call convention to a lib" do
    result = semantic(<<-CRYSTAL)
      @[CallConvention("X86_StdCall")]
      lib LibFoo
        fun foo : Int32
      end
      CRYSTAL
    foo = result.program.types["LibFoo"].lookup_first_def("foo", nil).as(External)
    foo.call_convention.should eq(LLVM::CallConvention::X86_StdCall)
  end

  it "errors if wrong number of arguments for CallConvention" do
    assert_error <<-CRYSTAL, "wrong number of arguments for annotation CallConvention (given 2, expected 1)"
      lib LibFoo
        @[CallConvention("X86_StdCall", "bar")]
        fun foo : Int32
      end
      CRYSTAL
  end

  it "errors if CallConvention argument is not a string" do
    assert_error <<-CRYSTAL, "argument to CallConvention must be a string"
      lib LibFoo
        @[CallConvention(1)]
        fun foo : Int32
      end
      CRYSTAL
  end

  it "errors if CallConvention argument is not a valid string" do
    assert_error <<-CRYSTAL, "invalid call convention. Valid values are #{LLVM::CallConvention.values.join ", "}"
      lib LibFoo
        @[CallConvention("foo")]
        fun foo : Int32
      end
      CRYSTAL
  end

  it "errors if assigning void lib call to var (#4414)" do
    assert_error <<-CRYSTAL, "assigning Void return value of lib fun call has no effect"
      lib LibFoo
        fun foo
      end

      x = LibFoo.foo
      CRYSTAL
  end

  it "errors if passing void lib call to call argument (#4414)" do
    assert_error <<-CRYSTAL, "passing Void return value of lib fun call has no effect"
      lib LibFoo
        fun foo
      end

      def bar(x)
      end

      bar(LibFoo.foo)
      CRYSTAL
  end

  it "can list lib functions at the top level (#12395)" do
    assert_type(<<-CRYSTAL) { bool }
      lib LibFoo
        fun foo
      end

      {% if LibFoo.methods.size == 1 %}
        true
      {% else %}
        1
      {% end %}
      CRYSTAL
  end
end
